//-----------------------------------------------------------------------------
// Copyright (c) 2006-2008 dhpoware. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "precompiled.h"
#include "Common.h"
#include <cmath>
#include "Camera_Alt.h"
#include "RenderDefines.h"

const float Camera_Alt::DEFAULT_FOVX = 90.0f;
const float Camera_Alt::DEFAULT_ZFAR = 1000.0f;
const float Camera_Alt::DEFAULT_ZNEAR = 0.1f;
const glm::vec3 Camera_Alt::WORLD_XAXIS(1.0f, 0.0f, 0.0f);
const glm::vec3 Camera_Alt::WORLD_YAXIS(0.0f, 1.0f, 0.0f);
const glm::vec3 Camera_Alt::WORLD_ZAXIS(0.0f, 0.0f, 1.0f);

const float EPSILON = 1e-6f;

static bool closeEnough(float f1, float f2)
{
	// Determines whether the two floating-point values f1 and f2 are
	// close enough together that they can be considered equal.

	return fabsf((f1 - f2) / ((f2 == 0.0f) ? 1.0f : f2)) < EPSILON;
}

Camera_Alt::Camera_Alt()
{

	m_fovx = DEFAULT_FOVX;
	m_znear = DEFAULT_ZNEAR;
	m_zfar = DEFAULT_ZFAR;
	m_aspectRatio = 0.0f;

	m_accumPitchDegrees = 0.0f;

	m_eye = glm::vec3(0.0f, 0.0f, 0.0f);
	m_xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
	m_yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
	m_zAxis = glm::vec3(0.0f, 0.0f, 1.0f);
	m_viewDir = glm::vec3(0.0f, 0.0f, -1.0f);

	m_acceleration = glm::vec3(0.0f, 0.0f, 0.0f);
	m_currentVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
	m_velocity = glm::vec3(0.0f, 0.0f, 0.0f);

	m_viewMatrix = glm::mat4(1);
	m_projMatrix = glm::mat4(1);
}

Camera_Alt::~Camera_Alt()
{
}

void Camera_Alt::lookAt(const glm::vec3 &target)
{
	lookAt(m_eye, target, m_yAxis);
}

void Camera_Alt::lookAt(const glm::vec3 &eye, const glm::vec3 &target, const glm::vec3 &up)
{
	/*m_eye = eye;

	m_zAxis = eye - target;
	m_zAxis = glm::normalize(m_zAxis);

	m_viewDir = -m_zAxis;

	m_xAxis = glm::vec3::cross(up, m_zAxis);
	m_xAxis = glm::normalize(m_xAxis);

	m_yAxis = glm::vec3::cross(m_zAxis, m_xAxis);
	m_yAxis = glm::normalize(m_yAxis);
	m_xAxis = glm::normalize(m_yAxis);

	m_viewMatrix[0][0] = m_xAxis.x;
	m_viewMatrix[1][0] = m_xAxis.y;
	m_viewMatrix[2][0] = m_xAxis.z;
	m_viewMatrix[3][0] = -glm::dot(m_xAxis, eye);

	m_viewMatrix[0][1] = m_yAxis.x;
	m_viewMatrix[1][1] = m_yAxis.y;
	m_viewMatrix[2][1] = m_yAxis.z;
	m_viewMatrix[3][1] = -glm::dot(m_yAxis, eye);

	m_viewMatrix[0][2] = m_zAxis.x;
	m_viewMatrix[1][2] = m_zAxis.y;
	m_viewMatrix[2][2] = m_zAxis.z;    
	m_viewMatrix[3][2] = -glm::dot(m_zAxis, eye);

	// Extract the pitch angle from the view matrix.
	m_accumPitchDegrees = Math::radiansToDegrees(-asinf(m_viewMatrix[1][2]));*/
	m_viewMatrix = glm::lookAt( eye, target, up );
}

void Camera_Alt::move(float dx, float dy, float dz)
{
	// Moves the Camera_Alt by dx world units to the left or right; dy
	// world units upwards or downwards; and dz world units forwards
	// or backwards.

	glm::vec3 eye = m_eye;
	glm::vec3 forwards;

	// Calculate the forwards direction. Can't just use the Camera_Alt's local
	// z axis as doing so will cause the Camera_Alt to move more slowly as the
	// Camera_Alt's view approaches 90 degrees straight up and down.

	forwards = glm::cross(WORLD_YAXIS, m_xAxis);
	forwards = glm::normalize(forwards);

	eye += m_xAxis * dx;
	eye += WORLD_YAXIS * dy;
	eye += forwards * dz;

	setPosition(eye);
}

void Camera_Alt::move(const glm::vec3 &direction, const glm::vec3 &amount)
{
	// Moves the Camera_Alt by the specified amount of world units in the specified
	// direction in world space.

	m_eye.x += direction.x * amount.x;
	m_eye.y += direction.y * amount.y;
	m_eye.z += direction.z * amount.z;

	updateViewMatrix(false);
}

void Camera_Alt::perspective(float fovx, float aspect, float znear, float zfar)
{
	// Construct a projection matrix based on the horizontal field of view
	// 'fovx' rather than the more traditional vertical field of view 'fovy'.

	/*float e = 1.0f / tanf(Math::degreesToRadians(fovx) / 2.0f);
	float aspectInv = 1.0f / aspect;
	float fovy = 2.0f * atanf(aspectInv / e);
	float xScale = 1.0f / tanf(0.5f * fovy);
	float yScale = xScale / aspectInv;

	m_projMatrix[0][0] = xScale;
	m_projMatrix[0][1] = 0.0f;
	m_projMatrix[0][2] = 0.0f;
	m_projMatrix[0][3] = 0.0f;

	m_projMatrix[1][0] = 0.0f;
	m_projMatrix[1][1] = yScale;
	m_projMatrix[1][2] = 0.0f;
	m_projMatrix[1][3] = 0.0f;

	m_projMatrix[2][0] = 0.0f;
	m_projMatrix[2][1] = 0.0f;
	m_projMatrix[2][2] = (zfar + znear) / (znear - zfar);
	m_projMatrix[2][3] = -1.0f;

	m_projMatrix[3][0] = 0.0f;
	m_projMatrix[3][1] = 0.0f;
	m_projMatrix[3][2] = (2.0f * zfar * znear) / (znear - zfar);
	m_projMatrix[3][3] = 0.0f;*/


	m_fovx = fovx;
	m_aspectRatio = aspect;
	m_znear = znear;
	m_zfar = zfar;
	m_projMatrix = glm::perspective(fovx,aspect,znear,zfar);
}

void Camera_Alt::rotate(float headingDegrees, float pitchDegrees, float rollDegrees)
{
	// Rotates the Camera_Alt based on its current behavior.
	// Note that not all behaviors support rolling.


	rotateFirstPerson(headingDegrees, pitchDegrees);


	m_accumHeadDegrees += headingDegrees;
	float range = 180.0f;
	if (m_headingDegrees > range)
	{
		m_accumHeadDegrees = 0;
	}

	if (m_headingDegrees < -range)
	{
		m_accumHeadDegrees = -0;
	}

	m_headingDegrees = m_accumHeadDegrees;
	m_rollDegrees=rollDegrees;
	m_pitchDegrees = m_accumPitchDegrees;

	updateViewMatrix(true);
}

void Camera_Alt::rotateFirstPerson(float headingDegrees, float pitchDegrees)
{
	m_accumPitchDegrees += pitchDegrees;

	if (m_accumPitchDegrees > 90.0f)
	{
		pitchDegrees = 90.0f - (m_accumPitchDegrees - pitchDegrees);
		m_accumPitchDegrees = 90.0f;
	}

	if (m_accumPitchDegrees < -90.0f)
	{
		pitchDegrees = -90.0f - (m_accumPitchDegrees - pitchDegrees);
		m_accumPitchDegrees = -90.0f;
	}

	glm::mat4 rotMtx;
	glm::vec4 v4_xAxis(m_xAxis.x, m_xAxis.y, m_xAxis.z, 1), 
		v4_yAxis(m_yAxis.x, m_yAxis.y, m_yAxis.z, 1), 
		v4_zAxis(m_zAxis.x, m_zAxis.y, m_zAxis.z, 1);

	// Rotate Camera_Alt's existing x and z axes about the world y axis.
	if (headingDegrees != 0.0f)
	{
		rotMtx = glm::rotate( glm::mat4(1), headingDegrees, WORLD_YAXIS);
		v4_xAxis = v4_xAxis * rotMtx;
		v4_zAxis = v4_zAxis * rotMtx;

		m_xAxis = glm::vec3( v4_xAxis );
		m_zAxis = glm::vec3( v4_zAxis );
	}

	// Rotate Camera_Alt's existing y and z axes about its existing x axis.
	if (pitchDegrees != 0.0f)
	{
		rotMtx = glm::rotate( glm::mat4(1), pitchDegrees, m_xAxis);
		v4_yAxis = v4_yAxis * rotMtx;
		v4_zAxis = v4_zAxis * rotMtx;

		m_yAxis = glm::vec3( v4_yAxis );
		m_zAxis = glm::vec3( v4_zAxis );
	}

	m_rotMatrix = glm::mat4( v4_xAxis, v4_yAxis, v4_zAxis, glm::vec4(0,0,0,1) );
}

void Camera_Alt::setAcceleration(float x, float y, float z)
{
	m_acceleration = glm::vec3(x, y, z);
}

void Camera_Alt::setAcceleration(const glm::vec3 &acceleration)
{
	m_acceleration = acceleration;
}


void Camera_Alt::setCurrentVelocity(float x, float y, float z)
{
	m_currentVelocity = glm::vec3(x, y, z);
}

void Camera_Alt::setCurrentVelocity(const glm::vec3 &currentVelocity)
{
	m_currentVelocity = currentVelocity;
}

void Camera_Alt::setPosition(float x, float y, float z)
{
	m_eye = glm::vec3(x, y, z);
	updateViewMatrix(false);
}

void Camera_Alt::setPosition(const glm::vec3 &position)
{
	m_eye = position;
	updateViewMatrix(false);
}

void Camera_Alt::setVelocity(float x, float y, float z)
{
	m_velocity = glm::vec3(x, y, z);
}

void Camera_Alt::setVelocity(const glm::vec3 &velocity)
{
	m_velocity = velocity;
}

void Camera_Alt::updatePosition(const glm::vec3 &direction, float elapsedTimeSec)
{
	// Moves the Camera_Alt using Newton's second law of motion. Unit mass is
	// assumed here to somewhat simplify the calculations. The direction vector
	// is in the range [-1,1].
	if (m_currentVelocity.length() != 0.0f)
	{
		// Only move the Camera_Alt if the velocity vector is not of zero length.
		// Doing this guards against the Camera_Alt slowly creeping around due to
		// floating point rounding errors.

		glm::vec3 displacement = (m_currentVelocity * elapsedTimeSec) +
			(0.5f * m_acceleration * elapsedTimeSec * elapsedTimeSec);

		// Floating point rounding errors will slowly accumulate and cause the
		// Camera_Alt to move along each axis. To prevent any unintended movement
		// the displacement vector is clamped to zero for each direction that
		// the Camera_Alt isn't moving in. Note that the updateVelocity() method
		// will slowly decelerate the Camera_Alt's velocity back to a stationary
		// state when the Camera_Alt is no longer moving along that direction. To
		// account for this the Camera_Alt's current velocity is also checked.

		if (direction.x == 0.0f && closeEnough(m_currentVelocity.x, 0.0f))
			displacement.x = 0.0f;

		if (direction.y == 0.0f && closeEnough(m_currentVelocity.y, 0.0f))
			displacement.y = 0.0f;

		if (direction.z == 0.0f && closeEnough(m_currentVelocity.z, 0.0f))
			displacement.z = 0.0f;

		move(displacement.x, displacement.y, displacement.z);
	}

	// Continuously update the Camera_Alt's velocity vector even if the Camera_Alt
	// hasn't moved during this call. When the Camera_Alt is no longer being moved
	// the Camera_Alt is decelerating back to its stationary state.

	//printf("elapsed: %f\n",elapsedTimeSec);
	//printf("cam move dir :(%f, %f, %f)\n", direction.x, direction.y, direction.z );
	//printf("cam vel :(%f, %f, %f)\n", m_velocity.x, m_velocity.y, m_velocity.z );
	//printf("cam cur vel :(%f, %f, %f)\n", m_currentVelocity.x, m_currentVelocity.y, m_currentVelocity.z );
	updateVelocity(direction, elapsedTimeSec);
}

void Camera_Alt::updateVelocity(const glm::vec3 &direction, float elapsedTimeSec)
{
	// Updates the Camera_Alt's velocity based on the supplied movement direction
	// and the elapsed time (since this method was last called). The movement
	// direction is in the range [-1,1].

	if (direction.x != 0.0f)
	{
		// Camera_Alt is moving along the x axis.
		// Linearly accelerate up to the Camera_Alt's max speed.

		m_currentVelocity.x += direction.x * m_acceleration.x * elapsedTimeSec;

		if (m_currentVelocity.x > m_velocity.x)
			m_currentVelocity.x = m_velocity.x;
		else if (m_currentVelocity.x < -m_velocity.x)
			m_currentVelocity.x = -m_velocity.x;
	}
	else
	{
		// Camera_Alt is no longer moving along the x axis.
		// Linearly decelerate back to stationary state.

		if (m_currentVelocity.x > 0.0f)
		{
			if ((m_currentVelocity.x -= m_acceleration.x * elapsedTimeSec) < 0.0f)
				m_currentVelocity.x = 0.0f;
		}
		else
		{
			if ((m_currentVelocity.x += m_acceleration.x * elapsedTimeSec) > 0.0f)
				m_currentVelocity.x = 0.0f;
		}
	}

	if (direction.y != 0.0f)
	{
		// Camera_Alt is moving along the y axis.
		// Linearly accelerate up to the Camera_Alt's max speed.

		m_currentVelocity.y += direction.y * m_acceleration.y * elapsedTimeSec;

		if (m_currentVelocity.y > m_velocity.y)
			m_currentVelocity.y = m_velocity.y;
		else if (m_currentVelocity.y < -m_velocity.y)
			m_currentVelocity.y = -m_velocity.y;
	}
	else
	{
		// Camera_Alt is no longer moving along the y axis.
		// Linearly decelerate back to stationary state.

		if (m_currentVelocity.y > 0.0f)
		{
			if ((m_currentVelocity.y -= m_acceleration.y * elapsedTimeSec) < 0.0f)
				m_currentVelocity.y = 0.0f;
		}
		else
		{
			if ((m_currentVelocity.y += m_acceleration.y * elapsedTimeSec) > 0.0f)
				m_currentVelocity.y = 0.0f;
		}
	}

	if (direction.z != 0.0f)
	{
		// Camera_Alt is moving along the z axis.
		// Linearly accelerate up to the Camera_Alt's max speed.

		m_currentVelocity.z += direction.z * m_acceleration.z * elapsedTimeSec;

		if (m_currentVelocity.z > m_velocity.z)
			m_currentVelocity.z = m_velocity.z;
		else if (m_currentVelocity.z < -m_velocity.z)
			m_currentVelocity.z = -m_velocity.z;
	}
	else
	{
		// Camera_Alt is no longer moving along the z axis.
		// Linearly decelerate back to stationary state.

		if (m_currentVelocity.z > 0.0f)
		{
			if ((m_currentVelocity.z -= m_acceleration.z * elapsedTimeSec) < 0.0f)
				m_currentVelocity.z = 0.0f;
		}
		else
		{
			if ((m_currentVelocity.z += m_acceleration.z * elapsedTimeSec) > 0.0f)
				m_currentVelocity.z = 0.0f;
		}
	}
}

void Camera_Alt::updateViewMatrix(bool orthogonalizeAxes)
{
	if (orthogonalizeAxes)
	{
		// Regenerate the Camera_Alt's local axes to orthogonalize them.

		m_zAxis = glm::normalize(m_zAxis);

		m_yAxis = glm::cross(m_zAxis, m_xAxis);
		m_yAxis = glm::normalize(m_yAxis);

		m_xAxis = glm::cross(m_yAxis, m_zAxis);
		m_xAxis = glm::normalize(m_xAxis);

		m_viewDir = -m_zAxis;
	}

	// Reconstruct the view matrix.

	m_viewMatrix[0][0] = m_xAxis.x;
	m_viewMatrix[1][0] = m_xAxis.y;
	m_viewMatrix[2][0] = m_xAxis.z;
	m_viewMatrix[3][0] = -glm::dot(m_xAxis, m_eye);

	m_viewMatrix[0][1] = m_yAxis.x;
	m_viewMatrix[1][1] = m_yAxis.y;
	m_viewMatrix[2][1] = m_yAxis.z;
	m_viewMatrix[3][1] = -glm::dot(m_yAxis, m_eye);

	m_viewMatrix[0][2] = m_zAxis.x;    
	m_viewMatrix[1][2] = m_zAxis.y;
	m_viewMatrix[2][2] = m_zAxis.z;   
	m_viewMatrix[3][2] = -glm::dot(m_zAxis, m_eye);

	m_viewMatrix[0][3] = 0.0f;
	m_viewMatrix[1][3] = 0.0f;
	m_viewMatrix[2][3] = 0.0f;
	m_viewMatrix[3][3] = 1.0f;
}

glm::mat4* Camera_Alt::getCameraMatrix( ){
	mProjViewMatrix = m_projMatrix * m_viewMatrix;
	return &mProjViewMatrix;
}

glm::vec2 Camera_Alt::get_mouseScreenDistanceFromCenter(){

	// Get the mouse_xy position.
	glm::ivec2 m(0);
	glfwGetMousePos(&m.x,&m.y);

	// Find our window size.
	int w = RENDER_WIDTH;
	int h = RENDER_HEIGHT;

	// Create our look_forward screen space.
	glm::ivec2 to_lookspace( w>>1, h>>1 );

	// Transform our mouse_xy to this space.
	m -= to_lookspace;

	// Normalize our mouse_xy to (-1_1).
	glm::vec2 m_normalized( 
		static_cast<float>(m.x) / static_cast<float>(to_lookspace.x), 
		static_cast<float>(m.y) / static_cast<float>(to_lookspace.y) );

	// Cap the normal values.
	m_normalized.x = THECLAMP( m_normalized.x, -1.0f, 1.0f );
	m_normalized.y = THECLAMP( m_normalized.y, -1.0f, 1.0f );

	//printf("lookspace mouse :(%f,%f)\n", m_normalized.x, m_normalized.y );

	return m_normalized;
}


const glm::vec3 Camera_Alt::getPosition() const
{ return m_eye; }

const glm::vec3 Camera_Alt::getEuler() const{
	return glm::vec3( m_headingDegrees, m_pitchDegrees, m_rollDegrees );
}